// => extensions of Matrix

#pragma once
#ifndef __MATRIX_EXT_H__
#define __MATRIX_EXT_H__

// Includes

#include <stddef.h>

#include "matrix.h"

// Types

/// @type: LURes
/// > the result of LU decomposition
/// @content: <matrixL> L part of decomposition [ Matrix * ]
/// @content: <matrixU> U part of decomposition [ Matrix * ]
/// @content: <factor> the factor of decomposition [ int ]
/// @descript: the factor only be `0`, `-1` or `1`
typedef struct LURes {
  Matrix *matrixL;
  Matrix *matrixU;
  int factor;
} LURes;

/// @type: EigSys
/// > the eigen system of matrix
/// @content: <eigvals> the eigen values of matrix [ double * ]
/// @content: <eigvecs> the eigen vectors of matrix [ Matrix * ]
/// @descript: the `eigvals` and `eigvecs` are one-to-one correspondence
typedef struct EigSys {
  double *eigvals;
  Matrix *eigvecs;
} EigSys;

// Functions

/// @func: InitMatrix
/// >> init matrix with specific size and default value
/// @param: <row> the row of the matrix [ size_t ]
/// @param: <col> the column of the matrix [ size_t ]
/// @param: <val> the default value of the matrix [ double ]
/// @return: the matrix [ Matrix * ]
Matrix *InitMatrix(size_t row, size_t col, double val);

/// @func: Ones
/// >> get a ones matrix with specific size
/// @param: <row> the row of the matrix [ size_t ]
/// @param: <col> the column of the matrix [ size_t ]
/// @return: a matrix fill with ones [ Matrix * ]
Matrix *Ones(size_t row, size_t col);

/// @func: InitMatrixFromInput
/// >> init matrix from user input, default by row
/// @return: the matrix [ Matrx * ]
/// @descript: exit when the input is invalid
Matrix *InitMatrixFromInput(void);

/// @func: InitMatricesFromFile
/// >> init matrices from file
/// @param: <filename> the file path [ const char * ]
/// @param: <count> the MAXIMUM number of matrices [ size_t ]
/// @return: matrices [ Matrix ** ]
/// @descript:
///   * exit when file is not exist
///   * exit when content style is wrong
///   * exit when read file error
Matrix **InitMatricesFromFile(const char *filename, size_t count);

/// @func: SaveMatrixToFile
/// >> save matrix data to file with text mode
/// @param: <filename> the file path [ const char * ]
/// @param: <mat> the matrix [ Matrix * ]
/// @descript: exit when `filename` or `mat` is null pointer
void SaveMatrixToFile(const char *filename, Matrix *mat);

/// @func: PrintMatrixP
/// >> print matrix with a specified precision
/// @param: <mat> the matrix [ const Matrix * ]
/// @param: <prec> the precison [ int ]
/// @descript: exit when `mat` is a null pointer
void PrintMatrixP(const Matrix *mat, int prec);

/// @func: MatPow
/// >> the power of matrix
/// @param: <m> the matrix [ Matrix * ]
/// @param: <n> the power [ size_t ]
/// @return: the result [ Matrix * ]
/// @descript: exit when the matrix is not a square matrix
Matrix *MatPow(Matrix *m, size_t n);

/// @func: CatMatrix
/// >> concatenate two matrix vertically
/// @param: <m1> the first matrix [ const Matrix * ]
/// @param: <m2> the second matrix [ const Matrix * ]
/// @return: the result of concatenation [ Matrix * ]
/// @descript:
///   * exit when `m1` or `m2` is a null pointer
///   * exit when the size of `m1` and `m2` are not matched
Matrix *CatMatrix(const Matrix *m1, const Matrix *m2);

/// @func: HorzCatMatrix
/// >> concatenate two matrix horizontally
/// @param: <m1> the first matrix [ const Matrix * ]
/// @param: <m2> the second matrix [ const Matrix * ]
/// @return: the result of concatenation [ Matrix * ]
/// @descript:
///   * exit when `m1` or `m2` is a null pointer
///   * exit when the size of `m1` and `m2` are not matched
Matrix *HorzCatMatrix(const Matrix *m1, const Matrix *m2);

/// @func: CopyMatrix
/// >> get a copy of matrix
/// @param: <mat> the matrix [ const Matrix * ]
/// @return: the copy of matrix [ Matrix * ]
/// @descript: exit when `mat` is null pointer
Matrix *CopyMatrix(const Matrix *mat);

/// @func: GetRowChangeMatrix
/// >> get a matrix that can change two rows
/// @param: <mat> the matrix [ const Matrix * ]
/// @param: <r1> the frist row [ size_t ]
/// @param: <r2> the second row [ size_t ]
/// @return: the row changed matrix [ Matrix * ]
/// @descript:
///   * exit when `mat` is a null pointer
///   * exit when row is out of boundary
Matrix *GetRowChangeMatrix(const Matrix *mat, size_t r1, size_t r2);

/// @func: DestroyLURes
/// >> decomposition lu res
/// @param: <lures> the lu res [ LURes * ]
void DestroyLURes(LURes *lures);

/// @func: MatrixLUDecomposition
/// >> decomposite a matrix in LU form
/// @param: <mat> the matrix [ const Matrix * ]
/// @return: the result [ LURes * ]
/// @descript: exit when `mat` is a null pointer
LURes *MatrixLUDecomposition(const Matrix *mat);

/// @func: MatrixACofactor
/// >> get the algebric cofactor of matrix
/// @param: <mat> the matrix[ const Matrix * ]
/// @param: <row> the row [ size_t ]
/// @param: <col> the column [ size_t ]
/// @return: the algebric cofactor  [ double ]
/// @descript:
///   * exit when `mat` is a null pointer
///   * exit when `row` or `col` is out of boundary
double MatrixACofactor(const Matrix *mat, size_t row, size_t col);

/// @func: InitAdjugateMatrix
/// >> get the adjugate of matrix
/// @param: <mat> the matrix [ const Matrix * ]
/// @return: the adjugate matrix [ Matrix * ]
/// @descript:
///   * exit when `mat` is not a square matrix
///   * exit when `mat` is a null pointer
Matrix *InitAdjugateMatrix(const Matrix *mat);

/// @func: LinearSolver
/// >> solve linear equations
/// @param: <A> the coefficient matrxi [ const Matrix * ]
/// @param: <b> the value vector [ const Matrix * ]
/// @return: the solution [ Matrix * ]
/// @descript:
///   * exit when `A` or `b` is null pointer
///   * exit when the size of `A` and `b` are not matched
///   * `b` must be column vector
Matrix *LinearSolver(const Matrix *A, const Matrix *b);

/// @func: JacobiLinearSolver
/// >> solve linear equations by Jacobi method
/// @param: <A> the coefficient matrix [ const Matrix * ]
/// @param: <b> the value vector [ const Matrix * ]
/// @param: <k> number of iterations [ size_t ]
/// @return: the solution [ Matrix * ]
/// @descript:
///   * exit when `A` or `b` is null pointer
///   * exit when the size of `A` and `b` are not matched
///   * `b` must be column vectors
Matrix *JacobiLinearSolver(const Matrix *A, const Matrix *b, size_t k);

/// @func: GetEigSystem {# TODO #}
/// >> get matrix eigen value and eigen vectors
/// @param: <mat> the matrix [ const Matrix * ]
/// @return: the eigen system [ EigSys * ]
/// @descript: exit when `mat` is a null pointer
EigSys *GetEigSystem(const Matrix *mat);

#endif
